//
//  DPNameServer.m
//  UITestingKit
//
//  Created by Ofri Wolfus on 27/01/07.
//  Copyright 2007 Ofri Wolfus. All rights reserved.
//

#import "DPNameServer.h"
#import <AppKit/AppKit.h>


@implementation DPNameServer

static inline void _DPUpdateNamesForObject(id obj) {
	[obj accessibilitySetOverrideValue:[(NSArray *)DPCopyNamesOfObject(obj) autorelease]
						  forAttribute:@"DPNames"];
}

+ (void)registerName:(NSString *)name withObject:(id)obj {
	DPRegisterNameWithObject((CFStringRef)name, obj);
	_DPUpdateNamesForObject(obj);
}

+ (void)unregisterName:(NSString *)name {
	id obj = [self objectNamed:name];
	DPUnregisterName((CFStringRef)name);
	_DPUpdateNamesForObject(obj);
}

+ (void)unregisterObject:(id)obj {
	DPUnregisterObject(obj);
	_DPUpdateNamesForObject(obj);
}

+ (NSArray *)namesOfObject:(id)obj {
	NSArray *a = (NSArray *)DPCopyNamesOfObject(obj);
	
	if ([a count])
		return [a autorelease];
	else {
		[a release];
		return nil;
	}
}

+ (id)objectNamed:(NSString *)name {
	return (id)DPGetObjectNamed((CFStringRef)name);
}

@end


#pragma mark -
#pragma mark CF Implementation
//======================================================//
//======================================================//
//============ CF NameServer Implementation ============//
//======================================================//
//======================================================//

// Our global namespace
static CFMutableDictionaryRef _namesTable = NULL;

// Set up _namesTable before main() is called
void __attribute__((constructor)) _DPSetUpNamesTable() {
	_namesTable = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
											&kCFTypeDictionaryKeyCallBacks, NULL);
}

void DPRegisterNameWithObject(CFStringRef name, void *obj) {
	CFDictionarySetValue(_namesTable, name, obj);
}

void DPUnregisterName(CFStringRef name) {
	CFDictionaryRemoveValue(_namesTable, name);
}

void DPUnregisterObject(void *obj) {
	CFArrayRef	names = DPCopyNamesOfObject(obj);
	CFIndex		i, count = CFArrayGetCount(names);
	
	for (i = 0U; i < count; i++) {
		const void *key = CFArrayGetValueAtIndex(names, i);
		
		if (key == obj)
			CFDictionaryRemoveValue(_namesTable, key);
	}
	
	CFRelease(names);
}

CFArrayRef DPCopyNamesOfObject(void *obj) {
	CFIndex i, count = CFDictionaryGetCount(_namesTable);
	CFStringRef *keys = calloc(count, sizeof(CFStringRef));
	void **objects = calloc(count, sizeof(void *));
	CFMutableArrayRef arr = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
	
	CFDictionaryGetKeysAndValues(_namesTable, (const void **)keys,
								 (const void **)objects);
	
	for (i = 0U; i < count; i++)
		if (objects[i] == obj)
			CFArrayAppendValue(arr, keys[i]);
	
	free(keys);
	free(objects);
	
	return arr;
}

const void *DPGetObjectNamed(CFStringRef name) {
	return CFDictionaryGetValue(_namesTable, name);
}


#pragma mark -
#pragma mark UITestingKit Additions
//======================================================//
//======================================================//
//=============== UITestingKit Additions ===============//
//======================================================//
//======================================================//

void DPNotifyTestingPreparedness(void) {
	// It's eaier to implement this in cocoa, but it requires carbon app to load
	// the objc runtime and the cocoa frameworks which is a bit of an overkill.
	// TODO: Implement this using CFBundle so it'll work for both cocoa and carbon.
	[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"DPApplicationFinishedLaunching"
																   object:[[[NSBundle mainBundle] executablePath] lastPathComponent]
																 userInfo:nil];
}


//#pragma mark -
//#pragma mark UITestingKit Support
//=======================================================//
//=======================================================//
//================ UITestingKit Support =================//
//=======================================================//
//=======================================================//

/*
 * Currently, we swizzle the implementations of
 * -accessibilityAttributeNames and -accessibilityAttributeValue:
 * of NSView, NSWindow and NSCell with our own
 * implementation that adds a @"DPNames" attribute.
 * Can we override NSObject's implementations instead?
 * Does NSObject even implement the accessibility API (doesn't seem so)?
 * Also, I have no idea if this implementation allows naming of menues,
 * but both NSMenu and NSMenuItem don't implement the NSAccessibility
 * informal protocol. WTF is going on??
 * If anyone knows a better solution, please let me know!
 */

// UITestingKit support
/*@interface NSView (UITesting)
@end
@implementation NSView (UITesting)

// NSView's -accessibilityAttributeNames implementation
static IMP _nsviewOrigAttrNames = NULL;
// NSView's -accessibilityAttributeValue: implementation
static IMP _nsviewOrigAttrVal = NULL;

id _DPViewAXAttrValue(id self, SEL _cmd, NSString *attr) {
	if ([attr isEqualToString:@"DPNames"])
		// I'm not sure the accessibility API can handle NULL values
		// so we fall back to the CF implementation that always returns
		// an array (even if it's empty).
		return [(NSArray *)DPCopyNamesOfObject(self) autorelease];
	else
		return _nsviewOrigAttrVal(self, _cmd, attr);
}

NSArray *_DPViewAXAttrNames(id self, SEL _cmd) {
	NSMutableArray *arr = [NSMutableArray arrayWithArray:_nsviewOrigAttrNames(self, _cmd)];
	[arr addObject:@"DPNames"];
	return arr;
}

// Replace NSView's implementation of accessibilityAttributeNames and
// accessibilityAttributeValue: with our own in order to add support for
// the "DPNames" attribute used by UITestingKit
+ (void)load {
	Method m = class_getInstanceMethod(self, @selector(accessibilityAttributeValue:));
	
	// -[NSView accessibilityAttributeValue:]
	_nsviewOrigAttrVal = method_getImplementation(m);
	method_setImplementation(m, (IMP)_DPViewAXAttrValue);
	
	// -[NSView accessibilityAttributeNames]
	m = class_getInstanceMethod(self, @selector(accessibilityAttributeNames));
	_nsviewOrigAttrNames = method_getImplementation(m);
	method_setImplementation(m, (IMP)_DPViewAXAttrNames);
}

@end

@interface NSWindow (UITesting)
@end
@implementation NSWindow (UITesting)

// NSWindow's -accessibilityAttributeNames implementation
static IMP _nswindowOrigAttrNames = NULL;
// NSWindow's -accessibilityAttributeValue: implementation
static IMP _nswindowOrigAttrVal = NULL;

id _DPWindowAXAttrValue(id self, SEL _cmd, NSString *attr) {
	if ([attr isEqualToString:@"DPNames"])
		// I'm not sure the accessibility API can handle NULL values
		// so we fall back to the CF implementation that always returns
		// an array (even if it's empty).
		return [(NSArray *)DPCopyNamesOfObject(self) autorelease];
	else
		return _nswindowOrigAttrVal(self, _cmd, attr);
}

NSArray *_DPWindowAXAttrNames(id self, SEL _cmd) {
	NSMutableArray *arr = [NSMutableArray arrayWithArray:_nswindowOrigAttrNames(self, _cmd)];
	[arr addObject:@"DPNames"];
	return arr;
}

// Replace NSWindow's implementation of accessibilityAttributeNames and
// accessibilityAttributeValue: with our own in order to add support for
// the "DPNames" attribute used by UITestingKit
+ (void)load {
	Method m = class_getInstanceMethod(self, @selector(accessibilityAttributeValue:));
	
	// -[NSWindow accessibilityAttributeValue:]
	_nswindowOrigAttrVal = method_getImplementation(m);
	method_setImplementation(m, (IMP)_DPWindowAXAttrValue);
	
	// -[NSWindow accessibilityAttributeNames]
	m = class_getInstanceMethod(self, @selector(accessibilityAttributeNames));
	_nswindowOrigAttrNames = method_getImplementation(m);
	method_setImplementation(m, (IMP)_DPWindowAXAttrNames);
}

@end

@interface NSCell (UITesting)
@end
@implementation NSCell (UITesting)

// NSCell's -accessibilityAttributeNames implementation
static IMP _nscellOrigAttrNames = NULL;
// NSCell's -accessibilityAttributeValue: implementation
static IMP _nscellOrigAttrVal = NULL;

id _DPCellAXAttrValue(id self, SEL _cmd, NSString *attr) {
	if ([attr isEqualToString:@"DPNames"])
		// I'm not sure the accessibility API can handle NULL values
		// so we fall back to the CF implementation that always returns
		// an array (even if it's empty).
		return [(NSArray *)DPCopyNamesOfObject(self) autorelease];
	else
		return _nscellOrigAttrVal(self, _cmd, attr);
}

NSArray *_DPCellAXAttrNames(id self, SEL _cmd) {
	NSMutableArray *arr = [NSMutableArray arrayWithArray:_nscellOrigAttrNames(self, _cmd)];
	[arr addObject:@"DPNames"];
	return arr;
}

// Replace NSCell's implementation of accessibilityAttributeNames and
// accessibilityAttributeValue: with our own in order to add support for
// the "DPNames" attribute used by UITestingKit
+ (void)load {
	Method m = class_getInstanceMethod(self, @selector(accessibilityAttributeValue:));
	
	// -[NSCell accessibilityAttributeValue:]
	_nscellOrigAttrVal = method_getImplementation(m);
	method_setImplementation(m, (IMP)_DPCellAXAttrValue);
	
	// -[NSCell accessibilityAttributeNames]
	m = class_getInstanceMethod(self, @selector(accessibilityAttributeNames));
	_nscellOrigAttrNames = method_getImplementation(m);
	method_setImplementation(m, (IMP)_DPCellAXAttrNames);
}

@end*/

